﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using System.Linq.Expressions;
using System.Reflection;
using NHibernate.Criterion;
using System.Collections;
using System.Web;

namespace Apollo.Core
{
    public class FilterRule
    {
        public FilterRule() { }

        public Projection Projection { get; set; }

        public RuleOperator Operator { get; set; }

        public string Data { get; set; }

        public ICriterion ToCriterion() {
            string nProjection = this.Projection.Alias;
            object castedData = null;
            List<object> castedDataList = new List<object>();
            Type castTo = null;

            if (this.Projection.IsRelation)
            {
                Entity entity = Application.Meta<Entity>()
                    .Single<Entity>(e => e.Name == this.Projection.ClassType.Name);

                nProjection += "." + entity.DisplayProperty;

                castTo = this.Projection.PropertyType.GetProperty(entity.DisplayProperty).PropertyType;

                if (this.Operator == RuleOperator.In) { 
                    // cast array of values
                    string[] parts = this.Data.Split(",".ToCharArray());

                    foreach (string part in parts)
                        castedDataList.Add(Apollo.Application.Deserialize(part, castTo));
                }
                else
                    // cast single value
                    castedData = this.Deserialize(castTo);
            }
            else {
                nProjection += "." + this.Projection.PropertyName;

                castTo = this.Projection.PropertyType;

                if (castTo.IsEnum) {
                    castedDataList = DeserializeEnum(castTo);

                    if(this.Operator == RuleOperator.Equals || this.Operator == RuleOperator.Contains
                        || this.Operator == RuleOperator.StartsWith || this.Operator == RuleOperator.EndsWith)
                        this.Operator = RuleOperator.In;
                    else if(this.Operator == RuleOperator.NotEqual)
                        this.Operator = RuleOperator.NotIn;
                }
                else if (this.Operator == RuleOperator.In)
                {
                    // cast array of values
                    string[] parts = this.Data.Split(",".ToCharArray());

                    foreach (string part in parts)
                        castedDataList.Add(Apollo.Application.Deserialize(part, castTo));
                }
                else
                    // cast single value
                    castedData = this.Deserialize(castTo);
            }

            switch(this.Operator) {
                case RuleOperator.IsNull:
                    return Expression.IsNull(nProjection);
                case RuleOperator.Equals:
                    return Expression.Eq(nProjection, castedData);
                case RuleOperator.NotNull:
                    return Expression.IsNotNull(nProjection);
                case RuleOperator.NotEqual:
                    return Expression.Not(Expression.Eq(nProjection, castedData));
                case RuleOperator.LessThan:
                    return Expression.Lt(nProjection, castedData);
                case RuleOperator.LessOrEqual:
                    return Expression.Le(nProjection, castedData);
                case RuleOperator.GraterThan:
                    return Expression.Gt(nProjection, castedData);
                case RuleOperator.GraterOrEqual:
                    return Expression.Ge(nProjection, castedData);
                case RuleOperator.StartsWith:
                    if(castTo != typeof(string))
                        goto case RuleOperator.Equals;
                    
                    return Expression.Like(nProjection, this.Data, MatchMode.Start);
                case RuleOperator.EndsWith:
                    if (castTo != typeof(string))
                        goto case RuleOperator.Equals;

                    return Expression.Like(nProjection, this.Data, MatchMode.End);
                case RuleOperator.Contains:
                    if (castTo != typeof(string))
                        goto case RuleOperator.Equals;

                    return Expression.Like(nProjection, this.Data, MatchMode.Anywhere);

                case RuleOperator.In:
                    return Expression.In(nProjection, castedDataList.ToArray());
                case RuleOperator.NotIn:
                    return Expression.Not(Expression.In(nProjection, castedDataList.ToArray()));

                case RuleOperator.Between:
                    return Expression.Between(nProjection, ((IList)castedData)[0], ((IList)castedData)[1]);
            }

            return null;
        }

        protected object Deserialize(Type castTo) {
            if (this.Data == "NULL")
                this.Operator = RuleOperator.IsNull;
            else if (this.Data == "NNULL")
                this.Operator = RuleOperator.NotNull;
            else if (this.Data == "BLANK")
            {
                this.Operator = RuleOperator.Equals;
                return "";
            }
            else if (this.Data == "NBLANK")
            {
                this.Operator = RuleOperator.NotEqual;
                return "";
            }
            else if (this.Data.StartsWith("$"))
                return this.DeserializeVar();


            if ((new Type[] { typeof(int), typeof(decimal), typeof(float), typeof(double), typeof(DateTime), typeof(int?), typeof(decimal?), typeof(float?), typeof(double?), typeof(DateTime?) }).Contains(castTo))
                return this.DeserializeNumericOrDateTime(castTo);
            else
                return Apollo.Application.Deserialize(this.Data, castTo);
                
        }

        protected object DeserializeNumericOrDateTime(Type castTo) {
            this.Data = this.Data.Replace(" ", "");

            if (this.Data.StartsWith(">="))
            {
                this.Operator = RuleOperator.GraterOrEqual;
                return Apollo.Application.Deserialize(this.Data.Substring(2), castTo);
            }
            else if (this.Data.StartsWith("<="))
            {
                this.Operator = RuleOperator.LessOrEqual;
                return Apollo.Application.Deserialize(this.Data.Substring(2), castTo);
            }
            else if (this.Data.Contains(">") && this.Data.Contains("<"))
            {
                this.Operator = RuleOperator.Between;

                int indexOfDif = this.Data.IndexOf("<");

                List<object> castedData = new List<object>();
                castedData.Add(Apollo.Application.Deserialize(this.Data.Substring(1, indexOfDif - 1), castTo));
                castedData.Add(Apollo.Application.Deserialize(this.Data.Substring(indexOfDif + 1), castTo));

                return castedData;
            }
            else if (this.Data.StartsWith(">"))
            {
                this.Operator = RuleOperator.GraterThan;
                return Apollo.Application.Deserialize(this.Data.Substring(1), castTo);
            }
            else if (this.Data.StartsWith("<"))
            {
                this.Operator = RuleOperator.LessThan;
                return Apollo.Application.Deserialize(this.Data.Substring(1), castTo);
            }
            else if (this.Data.Contains("|") || this.Data.Contains(";"))
            {
                this.Operator = RuleOperator.Between;

                List<string> separators = new List<string>() { "|", ";" };
                int indexOfSep = int.MaxValue;
                foreach (string separator in separators)
                {
                    int newIndexOfSep = this.Data.IndexOf(separator);
                    if (newIndexOfSep > 0 && indexOfSep > newIndexOfSep)
                        indexOfSep = newIndexOfSep;
                }

                List<object> castedData = new List<object>();
                castedData.Add(Apollo.Application.Deserialize(this.Data.Substring(0, indexOfSep), castTo));
                castedData.Add(Apollo.Application.Deserialize(this.Data.Substring(indexOfSep + 1), castTo));

                return castedData;
            }


            return Apollo.Application.Deserialize(this.Data, castTo);
        }

        protected List<object> DeserializeEnum(Type castTo)
        {
            List<object> castedDataList = new List<object>();

            if (this.Operator == RuleOperator.Equals || this.Operator == RuleOperator.NotEqual) {
                try { castedDataList.Add(System.Enum.Parse(castTo, this.Data)); }
                catch { }
            }
            else if (this.Operator == RuleOperator.StartsWith) {
                try { castedDataList.Add(System.Enum.Parse(castTo, this.Data)); }
                catch { }

                foreach (string enumValue in System.Enum.GetNames(castTo)) {
                    if (enumValue.ToLower().StartsWith(this.Data.ToLower()))
                        castedDataList.Add(System.Enum.Parse(castTo, enumValue));
                }
            }
            else if (this.Operator == RuleOperator.EndsWith) {
                try { castedDataList.Add(System.Enum.Parse(castTo, this.Data)); }
                catch { }

                foreach (string enumValue in System.Enum.GetNames(castTo)) {
                    if (enumValue.ToLower().EndsWith(this.Data.ToLower()))
                        castedDataList.Add(System.Enum.Parse(castTo, enumValue));
                }
            }
            else if (this.Operator == RuleOperator.Contains) {
                try { castedDataList.Add(System.Enum.Parse(castTo, this.Data)); }
                catch { }

                foreach (string enumValue in System.Enum.GetNames(castTo)) {
                    if (enumValue.ToLower().Contains(this.Data.ToLower()))
                        castedDataList.Add(System.Enum.Parse(castTo, enumValue));
                }
            }

            return castedDataList;
        }

        protected object DeserializeVar() {
            Type userType = Apollo.Application.ConfigurationSection.Apollo.UserType ?? typeof(User);

            if (this.Data.StartsWith("$user.")) {
                Projection userProjection = new Projection(this.Data.Substring(6), userType);

                return userProjection.Apply(Apollo.Application.CurrentUser);
            }
            else if (this.Data.StartsWith("$now")) {
                return DateTime.Now;
            }
            else if (this.Data.StartsWith("$today")) {
                return DateTime.Today;
            }

            return null;
        }
    }

    public enum RuleOperator { 
        /// <summary>
        /// ==
        /// </summary>
        Equals, 

        /// <summary>
        /// !=
        /// </summary>
	    NotEqual,

        /// <summary>
        /// begins with
        /// </summary>
	    StartsWith,

        /// <summary>
        /// <
        /// </summary>
	    LessThan, 

        /// <summary>
        /// <=
        /// </summary>
	    LessOrEqual,

        /// <summary>
        /// >
        /// </summary>
	    GraterThan,

        /// <summary>
        /// >=
        /// </summary>
	    GraterOrEqual,

        /// <summary>
        /// ends with
        /// </summary>
	    EndsWith, 

        /// <summary>
        /// contains
        /// </summary>
	    Contains, 

        /// <summary>
        /// is null
        /// </summary>
	    IsNull, 

        /// <summary>
        /// is not null
        /// </summary>
	    NotNull, 

        In,

        NotIn,

        /// <summary>
        /// It is used only internal
        /// </summary>
        Between,
    }
}